/**
 * Copyright [2019-Present] [starBlues]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gitee.starblues.loader.classloader.resource.storage;

import com.gitee.starblues.loader.classloader.resource.Resource;
import com.gitee.starblues.loader.classloader.resource.loader.DefaultResource;
import com.gitee.starblues.loader.utils.IOUtils;
import com.gitee.starblues.loader.utils.ObjectUtils;
import com.gitee.starblues.loader.utils.ResourceUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 抽象的资源存储
 *
 * @author starBlues
 * @since 3.0.0
 * @version 3.1.2
 */
public abstract class AbstractResourceStorage implements ResourceStorage {

    private final Set<URL> baseUrls = Collections.synchronizedSet(new HashSet<>());
    private final ConcurrentLinkedDeque<URL> hotUrls = new ConcurrentLinkedDeque<>();
    private final ConcurrentHashMap<String, ReentrantReadWriteLock> resourceLocks = new ConcurrentHashMap<>();
    private final Set<InputStream> inputStreams = Collections.newSetFromMap(new WeakHashMap<InputStream, Boolean>());

    @Override
    public void addBaseUrl(URL url){
        if(url == null){
            return;
        }
        baseUrls.add(url);
    }

    @Override
    public List<URL> getBaseUrl(){
        return new ArrayList<>(baseUrls);
    }


    @Override
    public final void add(Resource resource) throws Exception {
        addResource(resource);
    }

    @Override
    public void close() throws Exception {
        synchronized (inputStreams) {
            for (InputStream inputStream : inputStreams) {
                IOUtils.closeQuietly(inputStream);
            }
            inputStreams.clear();
        }
        hotUrls.clear();
        baseUrls.clear();
        resourceLocks.clear();
    }

    /**
     * 子类添加资源
     * @param resource 资源
     * @throws Exception 添加移除
     */
    protected abstract void addResource(Resource resource) throws Exception;

    /**
     * 格式化资源名称
     * @param name 资源名称
     * @return String
     */
    protected final String formatResourceName(String name) {
        return ResourceUtils.formatStandardName(name);
    }

    protected final InputStream openStream(Resource resource){
        if(resource == null){
            return null;
        }
        try {
            InputStream inputStream = resource.getUrl().openStream();
            synchronized (inputStreams) {
                inputStreams.add(inputStream);
            }
            return new AutoCloseInputStream(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    private class AutoCloseInputStream extends InputStream {
        private final InputStream delegate;
        private volatile boolean closed = false;

        public AutoCloseInputStream(InputStream delegate) {
            this.delegate = delegate;
        }

        @Override
        public int read() throws IOException {
            return delegate.read();
        }

        @Override
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                delegate.close();
                synchronized (inputStreams) {
                    inputStreams.remove(delegate);
                }
            }
        }

        @Override
        protected void finalize() throws Throwable {
            if (!closed) {
                close();
            }
            super.finalize();
        }
    }
    protected final Enumeration<InputStream> openStream(Enumeration<Resource> resources){
        if(resources == null){
            return Collections.emptyEnumeration();
        }
        return new Enumeration<InputStream>() {
            @Override
            public boolean hasMoreElements() {
                return resources.hasMoreElements();
            }

            @Override
            public InputStream nextElement() {
                Resource resource = resources.nextElement();
                return openStream(resource);
            }
        };
    }

    protected final void closeResources(List<Resource> resources){
        if(ObjectUtils.isEmpty(resources)){
            return;
        }
        for (Resource resource : resources) {
            IOUtils.closeQuietly(resource);
        }
        resources.clear();
    }

    // protected final synchronized Resource searchResource(String name) {
    //     Set<URL> searchUrl = new HashSet<>();
    //     URL matchBaseUrl = null;
    //     URL matchExistUrl = null;
    //     // TODO 还需优化
    //     while (true){
    //         URL baseUrl = hotUrls.pollFirst();
    //         if(baseUrl == null){
    //             break;
    //         }
    //         searchUrl.add(baseUrl);
    //         URL existUrl = ResourceUtils.getExistUrl(baseUrl, name);
    //         if(existUrl != null){
    //             matchBaseUrl = baseUrl;
    //             matchExistUrl = existUrl;
    //             hotUrls.addFirst(baseUrl);
    //             break;
    //         }
    //     }
    //
    //     if(matchBaseUrl == null){
    //         List<URL> baseUrls = getBaseUrl();
    //         for (URL baseUrl : baseUrls) {
    //             if(searchUrl.contains(baseUrl)){
    //                 continue;
    //             }
    //             URL existUrl = ResourceUtils.getExistUrl(baseUrl, name);
    //             if(existUrl != null){
    //                 matchBaseUrl = baseUrl;
    //                 matchExistUrl = existUrl;
    //                 hotUrls.addFirst(baseUrl);
    //                 break;
    //             }
    //         }
    //     }
    //     if(matchBaseUrl != null){
    //         try {
    //             Resource resource = new DefaultResource(name, matchBaseUrl, matchExistUrl);
    //             addResource(resource);
    //             return resource;
    //         } catch (Exception e) {
    //             return null;
    //         }
    //     }
    //     return null;
    // }
    protected final Resource searchResource(String name) {
        if (ObjectUtils.isEmpty(name)) {
            return null;
        }

        String resourceKey = formatResourceName(name);
        ReentrantReadWriteLock lock = resourceLocks.computeIfAbsent(resourceKey, k -> new ReentrantReadWriteLock());

        // 第一阶段：无锁快速路径 - 检查热URL缓存
        Resource hotResource = searchHotUrls(resourceKey);
        if (hotResource != null){
            return hotResource;
        }

        // 第二阶段：读锁保护 - 搜索baseUrls
        lock.readLock().lock();
        try {
            Resource baseResource = searchBaseUrls(resourceKey);
            if (baseResource != null) {
                // 添加到热URL缓存
                URL baseUrl = baseResource.getBaseUrl();
                if (baseUrl != null && !hotUrls.contains(baseUrl)) {
                    hotUrls.offerFirst(baseUrl);
                    // 限制热URL缓存大小
                    if (hotUrls.size() > 100) {
                        hotUrls.pollLast();
                    }
                }
                return baseResource;
            }
        } finally {
            lock.readLock().unlock();
        }

        return null;
    }
    /**
     * 无锁搜索热URL
     */
    private Resource searchHotUrls(String name) {
        Iterator<URL> iterator = hotUrls.iterator();
        while (iterator.hasNext()) {
            URL baseUrl = iterator.next();
            URL existUrl = ResourceUtils.getExistUrl(baseUrl, name);
            if (existUrl != null) {
                // 将找到的URL移到队列前端
                hotUrls.remove(baseUrl);
                hotUrls.offerFirst(baseUrl);
                return createAndCacheResource(name, baseUrl, existUrl);
            }
        }
        return null;
    }

    /**
     * 读锁保护下搜索baseUrls
     */
    private Resource searchBaseUrls(String name) {
        for (URL baseUrl : baseUrls) {
            URL existUrl = ResourceUtils.getExistUrl(baseUrl, name);
            if (existUrl != null) {
                return createAndCacheResource(name, baseUrl, existUrl);
            }
        }
        return null;
    }

    /**
     * 创建并缓存资源
     */
    private Resource createAndCacheResource(String name, URL baseUrl, URL existUrl) {
        try {
            Resource resource = new DefaultResource(name, baseUrl, existUrl);
            addResourceWrapper(resource);
            return resource;
        } catch (Exception e) {
            return null;
        }
    }
    protected final Enumeration<Resource> searchResources(String name){
        List<URL> baseUrls = getBaseUrl();
        return new InternalEnumeration(baseUrls, name);
    }


    private class InternalEnumeration implements Enumeration<Resource>{
        private final Iterator<URL> baseUrlIterator;
        private Resource nextResource = null;
        // private final List<URL> baseUrls;
        private final String name;

        private int index = 0;
        private Resource resource = null;

        private InternalEnumeration(List<URL> baseUrls, String name) {
            this.baseUrlIterator = new ArrayList<>(baseUrls).iterator();
            this.name = name;
        }

        @Override
        public boolean hasMoreElements() {
            if (nextResource != null) return true;
            return findNextResource();
        }

        @Override
        public Resource nextElement() {
            if (!hasMoreElements()) {
                throw new NoSuchElementException();
            }
            Resource resource = nextResource;
            nextResource = null;
            return resource;
        }

        private boolean findNextResource() {
            while (baseUrlIterator.hasNext()) {
                URL baseUrl = baseUrlIterator.next();
                URL existUrl = ResourceUtils.getExistUrl(baseUrl, name);
                if (existUrl != null) {
                    try {
                        nextResource = new DefaultResource(name, baseUrl, existUrl);
                        addResourceWrapper(nextResource);
                        return true;
                    } catch (Exception e) {
                        // 继续查找下一个
                    }
                }
            }
            return false;
        }
    }

    private void addResourceWrapper(Resource resource){
        if(resource == null){
            return;
        }
        try {
            addResource(resource);
        } catch (Exception e){
            // 忽略异常
        }
    }

}
